﻿/*
   Copyright 2012 University of Southampton

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml.Schema;
using System.Xml;
using System.Net;
using System.Collections;
using uk.ac.soton.ses;
using System.Xml.Linq;

namespace uk.ac.soton.ses.DAL
{
    public class HDCFileAccess
    {
        public const string mdcRoot = "https://mdc-s1.soton.ac.uk";
        public const string mdcDocLib = "mdcdata";
        private static HDCConfigurationOptions _configOptions = null;

        static HDCFileAccess()
        {
            HDCFileAccess._configOptions = HDCConfiguration.GetConfigurationOptions();
        }

        private static string GetRandomFileName()
        {
            string newName = Path.GetRandomFileName();
            return newName;
        }

        public static EP2DCDataTypes.MDCid CreateContainer(string userToken)
        {
            string basePath = HDCFileAccess._configOptions.FileSystemWatcherOptions.LocalPath;
            string newContainerPath ="";
            
            //Create a new folder
            do
            {
                newContainerPath = Path.Combine(basePath, HDCFileAccess.GetRandomFileName());
            } while (Directory.Exists(newContainerPath));
            Directory.CreateDirectory(newContainerPath);

            //Find it in the database and return its ID
            using (MaterialsMetadataContext dbcontext = new MaterialsMetadataContext(HDCFileAccess._configOptions.DatabaseOptions.EntityConnectionString))
            {
                int check = 1;
                Experiment newExperiment = null;
                while (check <= 10 && newExperiment == null)
                {
                    newExperiment = dbcontext.Experiments.FirstOrDefault(ex => !ex.IsDeleted && ex.DefaultBasePath == newContainerPath);

                    //If we didn't find a record yet, wait for a bit and then check again
                    if (newExperiment == null)
                    {
                        System.Threading.Thread.Sleep(1000);
                        check++;
                    }
                }

                //If we still couldn't find it, throw an exception
                if (newExperiment == null)
                {
                    string message = string.Format("Could not find folder {0}", newContainerPath);
                    throw new EP2DCDataTypes.UploadFileException(message);
                }

                return new EP2DCDataTypes.MDCid { MDCIDString = newExperiment.ID.ToString() };
            }
        }

        public static EP2DCDataTypes.MDCid AddUriToContainer(string userToken, string containerID, Uri uri)
        {
            EP2DCDataTypes.MDCid mdcId = new EP2DCDataTypes.MDCid();
            if (containerID == string.Empty)
            {
                string message = "Error: Container not specified.";
                throw new EP2DCDataTypes.UploadFileException(message);
            }
            /*
            using (SPSite siteCollection = new SPSite(mdcRoot))
            {
                using (SPWeb site = siteCollection.OpenWeb())
                {
                    SPFolder mdcContainer = site.GetFolder(new Guid(containerID));//, containerID));
                    if (mdcContainer.Exists)
                    {
                        SPContentType mdcContentType = site.ContentTypes["Link to a Document"];
                        SPFieldUrlValue urlFieldValue = new SPFieldUrlValue();
                        urlFieldValue.Url = uri.AbsoluteUri;
                        urlFieldValue.Description = uri.AbsoluteUri;
                        Hashtable hash = new Hashtable();
                        hash.Add("ContentType", mdcContentType.Name);
                        hash.Add("URL", urlFieldValue.ToString());

                        string format =  "<%@ Assembly Name='{0}' %>\r\n                <%@ Register TagPrefix='SharePoint' Namespace='Microsoft.SharePoint.WebControls' Assembly='Microsoft.SharePoint' %>\r\n                <%@ Import Namespace='System.IO' %>\r\n                <%@ Import Namespace='Microsoft.SharePoint' %>\r\n                <%@ Import Namespace='Microsoft.SharePoint.Utilities' %>\r\n                <%@ Import Namespace='Microsoft.SharePoint.WebControls' %>\r\n                <html xmlns:mso=\"urn:schemas-microsoft-com:office:office\" xmlns:msdt=\"uuid:C2F41010-65B3-11d1-A29F-00AA00C14882\">\r\n                <Head> <META name='WebPartPageExpansion' content='full'><META Name='progid' Content='SharePoint.Link'>\r\n<!--[if gte mso 9]><xml>\r\n<mso:CustomDocumentProperties>\r\n<mso:ContentType msdt:dt=\"string\">Link to a Document</mso:ContentType><mso:URL msdt:dt=\"string\">{1}</mso:URL>\r\n</mso:CustomDocumentProperties>\r\n</xml><![endif]-->\r\n                <body>\r\n                <form id='Form1' runat='server'>\r\n                <SharePoint:UrlRedirector id='Redirector1' runat='server' />\r\n                </form>\r\n                </body>\r\n                </html>";
                        StringBuilder redirectionHtml = new StringBuilder().AppendFormat(format, typeof(SPDocumentLibrary).Assembly.FullName, urlFieldValue.ToString());

                        string fileName = Guid.NewGuid() + ".aspx";
                        while (site.GetFile(mdcContainer.ServerRelativeUrl.ToString() + "/" + fileName).Exists)
                            fileName = Guid.NewGuid() + ".aspx";
                        SPFile spf = mdcContainer.Files.Add(fileName.ToString(), UTF8Encoding.UTF8.GetBytes(redirectionHtml.ToString()), hash, true);
                        spf.Item["ContentType"] = mdcContentType.Name;
                        spf.Item.Update();
                        mdcId.MDCIDString = spf.UniqueId.ToString();
                    }
                    else
                    {
                        string message = "Error: container doesn't exist.";
                        throw new MDC.DataTypes.UploadFileException(message);
                    }
                }
            }
             */
            return mdcId;
        }
        public static EP2DCDataTypes.MDCid AddMDCIdToContainer(string userToken, EP2DCDataTypes.MDCid containerId, EP2DCDataTypes.MDCid fileId)
        {
            /*
            using (SPSite siteCollection = new SPSite(mdcRoot))
            {
                using (SPWeb site = siteCollection.OpenWeb())
                {
                    SPFile spFile = site.GetFile(new Guid(fileId.MDCIDString));
                    if (spFile.Exists)
                    {
                        string s = SPUtility.GetFullUrl(siteCollection, spFile.ServerRelativeUrl);
                        return AddUriToContainer(string.Empty, containerId.MDCIDString, new Uri(SPUtility.GetFullUrl(siteCollection, spFile.ServerRelativeUrl)));
                    }
                    else
                    {
                        throw new FileNotFoundException();
                    }
                }
            }
             */
            return new EP2DCDataTypes.MDCid();
        }

        public static EP2DCDataTypes.MDCid UploadStream(string userToken, string fileName, Stream stream, string containerID, bool validateXML)
        {
            //We need to save it first so we can validate it before we copy it into MDC
            string tempfilename = Path.GetTempFileName();
            EP2DCDataTypes.MDCid returnMessage = new EP2DCDataTypes.MDCid();
            using (FileStream tempFS = File.Create(tempfilename, Int16.MaxValue))
            {
                const int bufferSize = 4096;
                byte[] bytes = new byte[bufferSize];
                int numBytesRead;
                while ((numBytesRead = stream.Read(bytes, 0, bufferSize)) > 0)
                {
                    tempFS.Write(bytes, 0, numBytesRead);
                }//while
                tempFS.Flush();
                tempFS.Close();
            }//using


            //Try copying into the MDC
            try
            {
                int newId = CopyFileIntoMDC(tempfilename, fileName, containerID, validateXML);
                returnMessage.MDCIDString = newId.ToString();
            }
            catch (Exception e)
            {
                returnMessage.MDCIDString = "Error " + e.Message;
                return returnMessage;
            }
            File.Delete(tempfilename);
            return returnMessage;
        }

        public static int CopyFileIntoMDC(string sourceFileName, string newFileName, string containerID, bool validateXML)
        {
            //Check the filename is sensible
            if (newFileName == string.Empty)
            {
                string message = "Filename not specified.";
                throw new EP2DCDataTypes.UploadFileException(message);
            }

            //Check the container ID has been specified
            if (containerID == string.Empty)
            {
                string message = "Container not specified.";
                throw new EP2DCDataTypes.UploadFileException(message);
            }

            //Check we can parse the container ID
            int intExperimentID = -1;
            if (!int.TryParse(containerID, out intExperimentID))
            {
                throw new EP2DCDataTypes.UploadFileException("Container ID parse error");
            }

            //Validate the XML if necessary
            bool fileIsXml = (string.Compare(Path.GetExtension(newFileName), ".xml", true) == 0);
            bool xmlIsValid = true;
            if (fileIsXml && validateXML)
            {
                try
                {
                    using (FileStream fileStream = File.OpenRead(sourceFileName))
                    {
                        xmlIsValid = ValidateXML(fileStream);
                    }
                }
                catch (EP2DCDataTypes.XmlValidatorException)
                {
                    throw;
                }
            }
            if (!xmlIsValid)
            {
                throw new EP2DCDataTypes.UploadFileException("XML Validation error");
            }

            //Find out where the container is saved by getting the DefaultBasePath from the database
            string folderToSaveTo;
            string fileNameToSaveTo;
            using (MaterialsMetadataContext dbcontext = new MaterialsMetadataContext(_configOptions.DatabaseOptions.EntityConnectionString))
            {
                Experiment exp = dbcontext.Experiments.FirstOrDefault(ex => ex.ID == intExperimentID);
                if (exp == null)
                {
                    throw new EP2DCDataTypes.UploadFileException("Can't find container with ID");
                }
                folderToSaveTo = exp.DefaultBasePath;
                fileNameToSaveTo = Path.Combine(folderToSaveTo, newFileName);

                //Check directory exists
                if (!Directory.Exists(folderToSaveTo))
                {
                    throw new EP2DCDataTypes.UploadFileException("Container doesn't exist.");
                }

                //Check the file doesn't already exist
                if (File.Exists(fileNameToSaveTo)
                    || exp.ExperimentDataFiles.Count(df=>df.IsDeleted==false && df.GetFullPath().Equals(fileNameToSaveTo,StringComparison.OrdinalIgnoreCase))>0)
                {
                    throw new EP2DCDataTypes.UploadFileException("File already exists");
                }

                //We're now okay to copy the file
                File.Copy(sourceFileName, fileNameToSaveTo);

                //Wait for the record to appear in the database so we can return its ID
                int check = 1;
                ExperimentDataFile newFile = null;
                while (check <= 10 && newFile == null)
                {
                    exp.ExperimentDataFiles.Load(System.Data.Objects.MergeOption.OverwriteChanges);
                    newFile = exp.ExperimentDataFiles.FirstOrDefault(df => !df.IsDeleted && df.GetFullPath().Equals(fileNameToSaveTo,StringComparison.OrdinalIgnoreCase));

                    //If we didn't find a record yet, wait for a bit and then check again
                    if (newFile == null)
                    {
                        System.Threading.Thread.Sleep(1000);
                        check++;
                    }
                }
                if (newFile == null)
                {
                    string message = string.Format("Could not find file {0}", fileNameToSaveTo);
                    throw new EP2DCDataTypes.UploadFileException(message);
                }

                //We found a matching record so return its ID
                return newFile.ID;
            }
        }

        public static EP2DCDataTypes.MDCUri GetURIForMDCid(EP2DCDataTypes.MDCid mdcId)
        {
            EP2DCDataTypes.MDCUri returnUri = new EP2DCDataTypes.MDCUri();
            int intPassedID = -1;

            if (!int.TryParse(mdcId.MDCIDString, out intPassedID))
            {
                throw new Exception("Invalid ID passed");
            }
            returnUri.uri = GetURIForMDCid(intPassedID);
            return returnUri;
        }
        public static Uri GetURIForMDCid(int mdcID)
        {
            Uri returnUri = null;
            using (MaterialsMetadataContext dbcontext = new MaterialsMetadataContext(HDCFileAccess._configOptions.DatabaseOptions.EntityConnectionString))
            {
                ExperimentDataFile experimentDataFile = dbcontext.ExperimentDataFiles.FirstOrDefault(df => df.ID == mdcID);
                if (experimentDataFile == null)
                {
                    throw new Exception("ID does not exist");
                }
                returnUri = new Uri(experimentDataFile.GetWebPath(dbcontext));
            }
            return returnUri;
        }

        public static EP2DCDataTypes.MDCid GetMDCIDForObjectPath(string userToken, string objectPath)
        {
            Guid objectGuid = Guid.Empty;
            EP2DCDataTypes.MDCid objectMDCID = new EP2DCDataTypes.MDCid();
            /*
            using (SPSite siteCollection = new SPSite(MDCDataAccess.mdcRoot))
            {
                using (SPWeb site = siteCollection.OpenWeb())
                {
                    object spObject = site.GetFileOrFolderObject(MDCDataAccess.mdcDocLib + "/" + objectPath);
                    SPFile spFile = spObject as SPFile;
                    SPFolder spFolder = spObject as SPFolder;
                    if (spFile != null)
                    {
                        objectGuid = spFile.UniqueId;
                    }
                    else if (spFolder != null)
                    {
                        objectGuid = spFolder.UniqueId;
                    }
                }
            }
            if (objectGuid == Guid.Empty)
                throw new FileNotFoundException();
            else
            {
                objectMDCID.MDCIDString = objectGuid.ToString();
                return objectMDCID;
            }
             */
            return new EP2DCDataTypes.MDCid();
        }

        public static List<EP2DCDataTypes.MDCid> GetMDCIDsInContainer(string userToken, EP2DCDataTypes.MDCid mdcId)
        {
            List<EP2DCDataTypes.MDCid> listOfFiles = new List<EP2DCDataTypes.MDCid>();
            int intExperimentID = -1;
            if (int.TryParse(mdcId.MDCIDString, out intExperimentID))
            {
                using (MaterialsMetadataContext dbcontext = new MaterialsMetadataContext(HDCFileAccess._configOptions.DatabaseOptions.EntityConnectionString))
                {
                    Experiment exp = dbcontext.Experiments.FirstOrDefault(ex => ex.ID == intExperimentID);
                    if (exp != null)
                    {
                        listOfFiles.AddRange(exp.ExperimentDataFiles.Select(df => new EP2DCDataTypes.MDCid() { MDCIDString = df.ID.ToString() }));
                    }
                    else
                    {
                        throw new FileNotFoundException();
                    }
                }
            }
            return listOfFiles;
        }

        public static string GetHTMLForMDCid(int mdcId)
        {
            using (MaterialsMetadataContext dbcontext = new MaterialsMetadataContext(HDCFileAccess._configOptions.DatabaseOptions.EntityConnectionString))
            {
                ExperimentDataFile rootDataFile = dbcontext.GetExperimentDataFileByID(mdcId);
                if (rootDataFile == null)
                {
                    throw new FileNotFoundException();
                }
                if (!rootDataFile.ExperimentReference.IsLoaded)
                {
                    rootDataFile.ExperimentReference.Load();
                }


                XElement htmlList = new XElement("div");

                List<Experiment> exps_related = dbcontext.GetRelatedExperiments(rootDataFile.Experiment.ID);
                if (exps_related.Count > 0)
                {
                    htmlList.Add(new XElement("p", "Suggested experiments based on existing links:"));
                    htmlList.Add(
                        new XElement("ul",
                                exps_related
                                .Select(ex =>
                                    new XElement("li",
                                        new XElement("a",
                                            new XAttribute("href", ex.GetWebPath(dbcontext) ?? String.Empty),
                                            new XAttribute("target", "_blank"),
                                            ex.Name
                                        )//end of <a>
                                    )//end of <li>
                                )//end of Select
                            )//end of <ul>
                        );
                }

                List<Experiment> exps_sametype = dbcontext.GetExperimentsOfSameType(rootDataFile.Experiment.ID);
                if (exps_sametype.Count > 0)
                {
                    htmlList.Add(new XElement("p", "Experiments of the same type:"));
                    htmlList.Add(
                            new XElement("ul",
                                exps_sametype
                                .Select(ex =>
                                    new XElement("li",
                                        new XElement("a",
                                            new XAttribute("href", ex.GetWebPath(dbcontext) ?? String.Empty),
                                            new XAttribute("target", "_blank"),
                                            ex.Name
                                        )//end of <a>
                                    )//end of <li>
                                )//end of Select
                            )//end of <ul>
                        );
                }

                List<ExperimentDataFile> df_related = rootDataFile.GetRelatedItems();
                if (df_related.Count > 0)
                {
                    htmlList.Add(new XElement("p", "Data files of the same type:"));
                    htmlList.Add(
                            new XElement("ul",
                                df_related
                                .Select(df =>
                                    new XElement("li",
                                        new XElement("a",
                                            new XAttribute("href", df.GetWebPath(dbcontext) ?? String.Empty),
                                            new XAttribute("target", "_blank"),
                                            df.Filename
                                        )//end of <a>
                                    )//end of <li>
                                )//end of Select
                            )//end of <ul>
                        );
                }
                return htmlList.ToString();
            }
        }

        public static bool DeleteContainer(string userToken, string MDCcontainerID)
        {
            //throw new NotImplementedException();
            bool results = false;
            return results;
        }
        public static List<EP2DCDataTypes.DataTypeDescriptor> ListDataTypes(string userToken)
        {
            //throw new NotImplementedException();
            List<EP2DCDataTypes.DataTypeDescriptor> results = new List<EP2DCDataTypes.DataTypeDescriptor>();
            return results;

        }
        private static bool ValidateXML(FileStream fs)
        {

            XmlReader xmlReader;
            XmlReaderSettings xmlSettings;
            XmlUrlResolver xmlResolver = new XmlUrlResolver();

            xmlSettings = new XmlReaderSettings();
            xmlSettings.ValidationType = ValidationType.Schema;
            xmlSettings.ValidationFlags |= XmlSchemaValidationFlags.ProcessInlineSchema;
            xmlSettings.ValidationFlags |= XmlSchemaValidationFlags.ProcessSchemaLocation;
            xmlSettings.ValidationFlags |= XmlSchemaValidationFlags.ReportValidationWarnings;
            xmlSettings.ValidationEventHandler += new ValidationEventHandler(xmlSettings_ValidationEventHandler);
            xmlSettings.ConformanceLevel = ConformanceLevel.Document;
            xmlResolver.Credentials = CredentialCache.DefaultCredentials;
            xmlSettings.XmlResolver = xmlResolver;
            xmlReader = XmlReader.Create(fs, xmlSettings);
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3;
            while (xmlReader.Read())
            {
            }
            return true;
        }

        private static void xmlSettings_ValidationEventHandler(object sender, ValidationEventArgs e)
        {
            //throw e.Exception; //Something weird with this line. Only rethrows exception when Severity is Warning! Will throw new exception instead.
            throw new EP2DCDataTypes.XmlValidatorException(e.Exception.Message, e.Exception);

            //Possible exception types:
            //catch (XmlSchemaValidationException XmlExp)
            //{
            //    DisplayMessage(XmlExp.Message);
            //}
            //catch (XmlException XmlExp)
            //{
            //    DisplayMessage(XmlExp.Message);
            //}
            //catch (XmlSchemaException XmlSchExp)
            //{
            //    DisplayMessage(XmlSchExp.Message);
            //}
        }

    }
}
